// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包fuzz为使用
// “go test”构建的测试以及在测试
// 包中使用模糊功能的程序提供通用模糊功能。
package fuzz

import (
	"context"
	"crypto/sha256"
	"errors"
	"fmt"
	"internal/godebug"
	"io"
	"io/ioutil"
	"math/bits"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
	"sync"
	"time"
)

// CoordinateFuzzingOpts是一组用于协调模糊化的参数。
// 除非另有规定，否则零值对每个字段都有效。
type CoordinateFuzzingOpts struct {
	// Log是一个用于记录进度消息和警告的编写器。
	// 如果为零，则输入输出。将使用Discard。
	Log io.Writer

	// Timeout是加载语料库
	// 后，用于模糊化的挂钟时间量。如果为零，则没有时间限制。
	Timeout time.Duration

	// Limit是要生成和测试的随机值的数量。如果为零，
	// 生成值的数量将没有限制。
	Limit int64

	// MinimizeTimeout是在发现撞车机后，用于最小化
	// 的挂钟时间。如果为零，则没有时间限制。如果
	// MinimizeTimeout和MinimizeLimit都为零，那么最小化将被禁用。
	MinimizeTimeout time.Duration

	// MinimizeLimit是在发现崩溃后最小化时对fuzz函数进行的最大调用次数。如果为零，则没有
	// 限制。最小化时对fuzz函数的调用也计入
	// 限制。如果MinimizeTimeout和MinimizeLimit均为零，则
	// 最小化将被禁用。
	MinimizeLimit int64

	// parallel是并行运行的工作进程数。如果为零，
	// CoordinateFuzzing将运行GOMAXPROCS workers。
	Parallel int

	// Seed是模糊目标通过测试添加的种子值列表。F.在testdata中添加
	// 和。
	Seed []CorpusEntry

	// Types是构成语料库条目的类型列表。
	// 必须设置类型，并且必须与Seed中的值匹配。
	Types []reflect.Type

	// CorpusDir是一个目录，可以在其中写入包含使
	// 测试代码崩溃的值的文件。必须设置微粒。
	CorpusDir string

	// CacheDir是一个包含其他“有趣”值的目录。
	// 模糊程序可能会从中派生出新值，并在此处写入新值。
	CacheDir string
}

// 协调模糊化创建多个工作进程，并与
// 进行通信，以测试可能触发崩溃和暴露错误的随机输入。
// 工作进程与协调进程在同一目录下运行相同的二进制文件，
// 环境变量与协调进程相同。工作人员还使用与协调器相同的参数运行
// ，除了-test。fuzzworker 
// 参数列表前面的标志。
// 
// 如果发生崩溃，函数将返回一个错误，其中包含关于崩溃的信息
// 可以向用户报告。
func CoordinateFuzzing(ctx context.Context, opts CoordinateFuzzingOpts) (err error) {
	if err := ctx.Err(); err != nil {
		return err
	}
	if opts.Log == nil {
		opts.Log = io.Discard
	}
	if opts.Parallel == 0 {
		opts.Parallel = runtime.GOMAXPROCS(0)
	}
	if opts.Limit > 0 && int64(opts.Parallel) > opts.Limit {
		// 不要雇佣超过我们需要的工人。
		opts.Parallel = int(opts.Limit)
	}

	c, err := newCoordinator(opts)
	if err != nil {
		return err
	}

	if opts.Timeout > 0 {
		var cancel func()
		ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
		defer cancel()
	}

	// fuzzCtx用于阻止工人，例如，在发现撞车者后。
	fuzzCtx, cancelWorkers := context.WithCancel(ctx)
	defer cancelWorkers()
	doneC := ctx.Done()

	// 当工作人员遇到致命错误时，会调用stop。
	var fuzzErr error
	stopping := false
	stop := func(err error) {
		if err == fuzzCtx.Err() || isInterruptError(err) {
			// 抑制因SIGINT导致的取消错误和终止。
			// 这些消息没有帮助，因为用户触发了错误
			// （带^C），或者将打印另一条更有用的消息（崩溃）。
			err = nil
		}
		if err != nil && (fuzzErr == nil || fuzzErr == ctx.Err()) {
			fuzzErr = err
		}
		if stopping {
			return
		}
		stopping = true
		cancelWorkers()
		doneC = nil
	}

	// 确保我们发现的任何崩溃都被写入到语料库中，即使出现错误或中断，同时将其最小化。
	crashWritten := false
	defer func() {
		if c.crashMinimizing == nil || crashWritten {
			return
		}
		werr := writeToCorpus(&c.crashMinimizing.entry, opts.CorpusDir)
		if werr != nil {
			err = fmt.Errorf("%w\n%v", err, werr)
			return
		}
		if err == nil {
			err = &crashError{
				path: c.crashMinimizing.entry.Path,
				err:  errors.New(c.crashMinimizing.crasherMsg),
			}
		}
	}()

	// 开始工作。
	// TODO（jayconrod）：我们想支持模糊化不同的二进制文件吗？
	dir := "" // 同自我
	binPath := os.Args[0]
	args := append([]string{"-test.fuzzworker"}, os.Args[1:]...)
	env := os.Environ() // 同自我

	errC := make(chan error)
	workers := make([]*worker, opts.Parallel)
	for i := range workers {
		var err error
		workers[i], err = newWorker(c, dir, binPath, args, env)
		if err != nil {
			return err
		}
	}
	for i := range workers {
		w := workers[i]
		go func() {
			err := w.coordinate(fuzzCtx)
			if fuzzCtx.Err() != nil || isInterruptError(err) {
				err = nil
			}
			cleanErr := w.cleanup()
			if err == nil {
				err = cleanErr
			}
			errC <- err
		}()
	}

	// 主事件循环。
	// 在所有员工离职之前不要返回。我们通过
	// 即使在ctx被取消后也能从工人那里收到消息，从而避免了僵局。
	activeWorkers := len(workers)
	statTicker := time.NewTicker(3 * time.Second)
	defer statTicker.Stop()
	defer c.logStats()

	c.logStats()
	for {
		var inputC chan fuzzInput
		input, ok := c.peekInput()
		if ok && c.crashMinimizing == nil && !stopping {
			inputC = c.inputC
		}

		var minimizeC chan fuzzMinimizeInput
		minimizeInput, ok := c.peekMinimizeInput()
		if ok && !stopping {
			minimizeC = c.minimizeC
		}

		select {
		case <-doneC:
			// 中断、取消或超时。
			// stop将doneC设置为零，这样我们就不用忙着在这里等待了。
			stop(ctx.Err())

		case err := <-errC:
			// 一名工人被解雇，可能是因为遇到致命错误。
			stop(err)
			activeWorkers--
			if activeWorkers == 0 {
				return fuzzErr
			}

		case result := <-c.resultC:
			// 收到工人的回复。
			if stopping {
				break
			}
			c.updateStats(result)

			if result.crasherMsg != "" {
				if c.warmupRun() && result.entry.IsSeed {
					target := filepath.Base(c.opts.CorpusDir)
					fmt.Fprintf(c.opts.Log, "failure while testing seed corpus entry: %s/%s\n", target, testName(result.entry.Parent))
					stop(errors.New(result.crasherMsg))
					break
				}
				if c.canMinimize() && result.canMinimize {
					if c.crashMinimizing != nil {
						// 此崩溃没有最小化，另一个崩溃正在最小化。
						// 忽略这一个，等待另一个完成。
						break
					}
					// 发现了一个撞击机，但尚未尝试将其最小化。
					// 将其发送给工人，以最小化。禁用inputC以便
					// 其他工作人员不再继续模糊化。
					c.crashMinimizing = &result
					fmt.Fprintf(c.opts.Log, "fuzz: minimizing %d-byte failing input file\n", len(result.entry.Data))
					c.queueForMinimization(result, nil)
				} else if !crashWritten {
					// 找到了一个最小化或不可最小化的碰撞器。
					// 向语料库写入并停止。
					err := writeToCorpus(&result.entry, opts.CorpusDir)
					if err == nil {
						crashWritten = true
						err = &crashError{
							path: result.entry.Path,
							err:  errors.New(result.crasherMsg),
						}
					}
					if shouldPrintDebugInfo() {
						fmt.Fprintf(
							c.opts.Log,
							"DEBUG new crasher, elapsed: %s, id: %s, parent: %s, gen: %d, size: %d, exec time: %s\n",
							c.elapsed(),
							result.entry.Path,
							result.entry.Parent,
							result.entry.Generation,
							len(result.entry.Data),
							result.entryDuration,
						)
					}
					stop(err)
				}
			} else if result.coverageData != nil {
				if c.warmupRun() {
					if shouldPrintDebugInfo() {
						fmt.Fprintf(
							c.opts.Log,
							"DEBUG processed an initial input, elapsed: %s, id: %s, new bits: %d, size: %d, exec time: %s\n",
							c.elapsed(),
							result.entry.Parent,
							countBits(diffCoverage(c.coverageMask, result.coverageData)),
							len(result.entry.Data),
							result.entryDuration,
						)
					}
					c.updateCoverage(result.coverageData)
					c.warmupInputLeft--
					if c.warmupInputLeft == 0 {
						fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, gathering baseline coverage: %d/%d completed, now fuzzing with %d workers\n", c.elapsed(), c.warmupInputCount, c.warmupInputCount, c.opts.Parallel)
						if shouldPrintDebugInfo() {
							fmt.Fprintf(
								c.opts.Log,
								"DEBUG finished processing input corpus, elapsed: %s, entries: %d, initial coverage bits: %d\n",
								c.elapsed(),
								len(c.corpus.entries),
								countBits(c.coverageMask),
							)
						}
					}
				} else if keepCoverage := diffCoverage(c.coverageMask, result.coverageData); keepCoverage != nil {
					// 找到了一个扩展覆盖范围的值。
					// 这不是一个崩溃的程序，但我们可能希望将其添加到磁盘上的
					// 语料库中，并为将来的模糊化设置优先级。
					// TODO（jayconrod，katiehockman）：优先模糊这些扩展了覆盖范围的
					// 值，可能基于
					// 此结果扩展的新边数。
					// TODO（杰康罗德，凯蒂霍克曼）：不要写一个语料库中已经存在的值。
					if c.canMinimize() && result.canMinimize && c.crashMinimizing == nil {
						// 发送回workers，以找到保留至少一个新覆盖位的较小值。
						c.queueForMinimization(result, keepCoverage)
					} else {
						// 更新协调器的覆盖范围掩码并保存值。
						inputSize := len(result.entry.Data)
						entryNew, err := c.addCorpusEntries(true, result.entry)
						if err != nil {
							stop(err)
							break
						}
						if !entryNew {
							continue
						}
						c.updateCoverage(keepCoverage)
						c.inputQueue.enqueue(result.entry)
						c.interestingCount++
						if shouldPrintDebugInfo() {
							fmt.Fprintf(
								c.opts.Log,
								"DEBUG new interesting input, elapsed: %s, id: %s, parent: %s, gen: %d, new bits: %d, total bits: %d, size: %d, exec time: %s\n",
								c.elapsed(),
								result.entry.Path,
								result.entry.Parent,
								result.entry.Generation,
								countBits(keepCoverage),
								countBits(c.coverageMask),
								inputSize,
								result.entryDuration,
							)
						}
					}
				} else {
					if shouldPrintDebugInfo() {
						fmt.Fprintf(
							c.opts.Log,
							"DEBUG worker reported interesting input that doesn't expand coverage, elapsed: %s, id: %s, parent: %s, canMinimize: %t\n",
							c.elapsed(),
							result.entry.Path,
							result.entry.Parent,
							result.canMinimize,
						)
					}
				}
			} else if c.warmupRun() {
				// 在
				// 预热期间，没有报告此输入的错误或覆盖率数据，因此继续处理结果。
				c.warmupInputLeft--
				if c.warmupInputLeft == 0 {
					fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, testing seed corpus: %d/%d completed, now fuzzing with %d workers\n", c.elapsed(), c.warmupInputCount, c.warmupInputCount, c.opts.Parallel)
					if shouldPrintDebugInfo() {
						fmt.Fprintf(
							c.opts.Log,
							"DEBUG finished testing-only phase, elapsed: %s, entries: %d\n",
							time.Since(c.startTime),
							len(c.corpus.entries),
						)
					}
				}
			}

			// 处理完结果后，如果我们
			// 已达到模糊限制，请停止工作人员。
			if c.opts.Limit > 0 && c.count >= c.opts.Limit {
				stop(nil)
			}

		case inputC <- input:
			// 已将下一个输入发送给工作程序。
			c.sentInput(input)

		case minimizeC <- minimizeInput:
			// 将下一个最小化输入发送给工作人员。
			c.sentMinimizeInput(minimizeInput)

		case <-statTicker.C:
			c.logStats()
		}
	}

	// TODO（杰康罗德，凯蒂霍克曼）：如果一个撞车者不能写入语料库，
	// 改为写入缓存。
}

// crashError将一个写在种子库中的崩溃者包装起来。它保存导致崩溃的输入被保存的文件的名称
// 。测试
// 框架使用它来报告一个命令，以重新运行特定的输入。
type crashError struct {
	path string
	err  error
}

func (e *crashError) Error() string {
	return e.err.Error()
}

func (e *crashError) Unwrap() error {
	return e.err
}

func (e *crashError) CrashPath() string {
	return e.path
}

type corpus struct {
	entries []CorpusEntry
	hashes  map[[sha256.Size]byte]bool
}

// addCorpusEntries将条目添加到语料库中，并可选地将条目
// 写入缓存目录。如果语料库中已有条目，则跳过该条目。如果
// 所有条目都是唯一的，则addCorpusEntries返回true和nil错误；
// 如果至少有一个条目是重复的，则返回false和nil错误。
func (c *coordinator) addCorpusEntries(addToCache bool, entries ...CorpusEntry) (bool, error) {
	noDupes := true
	for _, e := range entries {
		data, err := corpusEntryData(e)
		if err != nil {
			return false, err
		}
		h := sha256.Sum256(data)
		if c.corpus.hashes[h] {
			noDupes = false
			continue
		}
		if addToCache {
			if err := writeToCorpus(&e, c.opts.CacheDir); err != nil {
				return false, err
			}
			// 对于写入磁盘的条目，我们不保留字节，因为语料库将消耗大量的
			// 内存。
			e.Data = nil
		}
		c.corpus.hashes[h] = true
		c.corpus.entries = append(c.corpus.entries, e)
	}
	return noDupes, nil
}

// CorpusEntry代表模糊化的单个输入。
// 
// 我们必须在testing and testing/internal/testdeps 
// 包中使用等效类型，但测试无法直接导入此包，我们不希望
// 从测试中导出此类型。相反，为了方便起见，我们使用相同的结构类型和
// 使用类型别名（不是定义的类型）。
type CorpusEntry = struct {
	Parent string

	// 如果条目是从磁盘加载的，则Path是语料库文件的路径。
	// 对于其他条目，包括f.Add提供的种子值，Path是测试的
	// 名称，例如种子#0或其散列。
	Path string

	// 数据是原始输入数据。只能为种子
	// 值填充数据。对于磁盘上的语料库文件，数据将为零，因为它将使用Path从磁盘加载
	// 。
	Data []byte

	// Values是从语料库文件中解包的值。
	Values []any

	Generation int

	// IsSeed表示该条目是否为种子库的一部分。
	IsSeed bool
}

// 从数据结构
// 字段或磁盘返回原始输入字节。
func corpusEntryData(ce CorpusEntry) ([]byte, error) {
	if ce.Data != nil {
		return ce.Data, nil
	}

	return os.ReadFile(ce.Path)
}

type fuzzInput struct {
	// entry是初始测试的值。工作者将从此起点随机变异
	// 值。
	entry CorpusEntry

	// 超时是对该输入进行模糊化处理的时间，
	// 不包括启动或清理。
	timeout time.Duration

	// limit是工作人员可以对fuzz函数进行的最大调用次数。例如，如果工作人员提前发现
	// 错误，则可能会减少呼叫次数。如果limit为零，则对
	// fuzz函数的调用没有限制。
	limit int64

	// 预热指示在模糊开始之前这是否是预热输入。如果
	// true，则不应模糊输入。
	warmup bool

	// Coverage数据反映协调员当前的Coverage任务。
	coverageData []byte
}

type fuzzResult struct {
	// 条目是一个有趣的值，或者是一个破坏者。
	entry CorpusEntry

	// crasherMsg是来自崩溃的错误消息。如果没有发现坠机的话，那就是“”。
	crasherMsg string

	// 如果工作人员试图最小化此结果，则canMinimize为真。
	// 可能是错误的，因为已经尝试过了。
	canMinimize bool

	// 如果工作人员发现新的保险范围，则设置coverage Data。
	coverageData []byte

	// limit是协调员要求工作人员测试的值的数量。如果没有限制，则为0。
	limit int64

	// count是工作人员实际测试的值的数量。
	count int64

	// totalDuration是工作人员测试输入的时间。
	totalDuration time.Duration

	// entryDuration是工作人员执行有趣结果所花费的时间
	entryDuration time.Duration
}

type fuzzMinimizeInput struct {
	// entry是一个有趣的值或要最小化的崩溃。
	entry CorpusEntry

	// crasherMsg是来自崩溃的错误消息。如果没有发现坠机的话，那就是“”。
	// 如果设置了，工作程序将尝试找到一个较小的输入，该输入也会产生一个错误，但不一定是相同的错误。
	crasherMsg string

	// limit是工作程序可以对fuzz函数进行的最大调用次数。例如，如果无法重现
	// 错误，则工作者可能会拨打更少的电话。如果limit为零，则对fuzz函数的调用没有限制。
	limit int64

	// 超时是将此输入最小化所花费的时间。
	// 零超时意味着没有限制。
	timeout time.Duration

	// keepCoverage是条目找到的不在协调器的组合集中的覆盖位的集合。最小化时，工作程序应该找到一个
	// 输入，该输入至少保留这些位中的一位。
	// 崩溃输入的keepCoverage为零。
	keepCoverage []byte
}

// 协调员拥有员工可以用来与协调员进行沟通的渠道。
type coordinator struct {
	opts CoordinateFuzzingOpts

	// startTime是加载语料库后我们开始工作的时间。
	// 用于日志记录。
	startTime time.Time

	// 协调器将值发送给fuzz。任何工作人员都可以从此通道接收
	// 值。工作人员将结果发送给resultC。
	inputC chan fuzzInput

	// minimizeC由协调器发送要最小化的值。任何工作者都可以
	// 从该通道接收值。工作人员将结果发送给resultC。
	minimizeC chan fuzzMinimizeInput

	// resultC是由工作人员发送的模糊结果。协调人
	// 收到这些信息。允许使用多种类型的消息。
	resultC chan fuzzResult

	// count是迄今为止模糊的值的数量。
	count int64

	// countLastLog是上次记录输出时模糊化的值数。
	countLastLog int64

	// timeLastLog是上次记录输出的时间。
	timeLastLog time.Time

	// interestingCount是在该执行中找到的唯一感兴趣值的数目。
	interestingCount int

	// warmupInputCount是语料库中所有条目的计数，这些条目将
	// 需要从工作人员那里接收，以便在预热期间运行一次，但不模糊。
	// 这可能用于覆盖率数据，或者仅用于验证
	// 种子库没有任何崩溃程序。见warmupRun。
	warmupInputCount int

	// warmupInputLeft是语料库中仍需要从工作人员处接收
	// 以便在预热期间运行一次的条目数，但不是fuzz。
	// 参见左侧的Warmupinput。
	warmupInputLeft int

	// 持续时间是指对员工内部进行模糊处理所花费的时间，不包括启动或拆除的时间。
	duration time.Duration

	// countWaiting是协调器正在等待工作人员完成的模糊执行数。
	countWaiting int64

	// 语料库是一组有趣的值，包括种子语料库和
	// 生成的值，工作人员报告称这些值是有趣的。
	corpus corpus

	// 如果可以最小化一种或多种类型的模糊
	// 函数的参数，则minimizationAllowed为真。
	minimizationAllowed bool

	// inputQueue是一个输入队列，工作人员应该尝试模糊化。这是
	// 最初由种子库和缓存输入填充。当发现新的覆盖范围时，可能会添加更多输入
	// 。
	inputQueue queue

	// minimizeQueue是导致错误或暴露新
	// 覆盖率的输入队列。工人们应该尝试寻找更小的输入，以实现
	// 同样的功能。
	minimizeQueue queue

	// 崩溃化是目前正在最小化的崩溃。
	crashMinimizing *fuzzResult

	// coverage任务聚合了在
	// 语料库中为所有输入找到的覆盖率。每个字节代表一个基本执行块。字节内的每个设置位
	// 表示一个输入至少触发了该块
	// 1次，其中n是该位在字节中的位置。例如，
	// 值为12表示单独的输入触发了该块
	// 的次数介于4-7次和8-15次之间。
	coverageMask []byte
}

func newCoordinator(opts CoordinateFuzzingOpts) (*coordinator, error) {
	// 确保所有种子语料库都已整理好数据。
	for i := range opts.Seed {
		if opts.Seed[i].Data == nil && opts.Seed[i].Values != nil {
			opts.Seed[i].Data = marshalCorpusFile(opts.Seed[i].Values...)
		}
	}
	c := &coordinator{
		opts:        opts,
		startTime:   time.Now(),
		inputC:      make(chan fuzzInput),
		minimizeC:   make(chan fuzzMinimizeInput),
		resultC:     make(chan fuzzResult),
		timeLastLog: time.Now(),
		corpus:      corpus{hashes: make(map[[sha256.Size]byte]bool)},
	}
	if err := c.readCache(); err != nil {
		return nil, err
	}
	if opts.MinimizeLimit > 0 || opts.MinimizeTimeout > 0 {
		for _, t := range opts.Types {
			if isMinimizable(t) {
				c.minimizationAllowed = true
				break
			}
		}
	}

	covSize := len(coverage())
	if covSize == 0 {
		fmt.Fprintf(c.opts.Log, "warning: the test binary was not built with coverage instrumentation, so fuzzing will run without coverage guidance and may be inefficient\n")
		// 即使只运行覆盖，我们仍然应该运行种子库的所有
		// 以确保在开始模糊之前没有存在故障。
		c.warmupInputCount = len(c.opts.Seed)
		for _, e := range c.opts.Seed {
			c.inputQueue.enqueue(e)
		}
	} else {
		c.warmupInputCount = len(c.corpus.entries)
		for _, e := range c.corpus.entries {
			c.inputQueue.enqueue(e)
		}
		// 将c.coverageMask设置为一个干净的[]字节，该字节中满是零。
		c.coverageMask = make([]byte, covSize)
	}
	c.warmupInputLeft = c.warmupInputCount

	if len(c.corpus.entries) == 0 {
		fmt.Fprintf(c.opts.Log, "warning: starting with empty corpus\n")
		var vals []any
		for _, t := range opts.Types {
			vals = append(vals, zeroValue(t))
		}
		data := marshalCorpusFile(vals...)
		h := sha256.Sum256(data)
		name := fmt.Sprintf("%x", h[:4])
		c.addCorpusEntries(false, CorpusEntry{Path: name, Data: data})
	}

	return c, nil
}

func (c *coordinator) updateStats(result fuzzResult) {
	c.count += result.count
	c.countWaiting -= result.limit
	c.duration += result.totalDuration
}

func (c *coordinator) logStats() {
	now := time.Now()
	if c.warmupRun() {
		runSoFar := c.warmupInputCount - c.warmupInputLeft
		if coverageEnabled {
			fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, gathering baseline coverage: %d/%d completed\n", c.elapsed(), runSoFar, c.warmupInputCount)
		} else {
			fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, testing seed corpus: %d/%d completed\n", c.elapsed(), runSoFar, c.warmupInputCount)
		}
	} else if c.crashMinimizing != nil {
		fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, minimizing\n", c.elapsed())
	} else {
		rate := float64(c.count-c.countLastLog) / now.Sub(c.timeLastLog).Seconds()
		if coverageEnabled {
			total := c.warmupInputCount + c.interestingCount
			fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, execs: %d (%.0f/sec), new interesting: %d (total: %d)\n", c.elapsed(), c.count, rate, c.interestingCount, total)
		} else {
			fmt.Fprintf(c.opts.Log, "fuzz: elapsed: %s, execs: %d (%.0f/sec)\n", c.elapsed(), c.count, rate)
		}
	}
	c.countLastLog = c.count
	c.timeLastLog = now
}

// peekInput返回下一个应该发送给工作者的值。
// 如果执行次数有限，则返回值包括
// 一个工作者的限制。如果没有执行剩余，peekInput将返回
// 一个零值和false。
// 
// peekInput实际上不会从队列中删除输入。调用方
// 必须在发送输入后调用sentInput。
// 
// 如果输入队列为空且仅覆盖/测试运行已完成，
// 队列将从语料库中重新填充它。
func (c *coordinator) peekInput() (fuzzInput, bool) {
	if c.opts.Limit > 0 && c.count+c.countWaiting >= c.opts.Limit {
		// 对fuzz函数的调用次数已达到最大值。
		// 现在不要发送更多输入。
		return fuzzInput{}, false
	}
	if c.inputQueue.len == 0 {
		if c.warmupRun() {
			// 在发送更多
			// 输入之前，等待覆盖率/测试仅运行完成。
			return fuzzInput{}, false
		}
		c.refillInputQueue()
	}

	entry, ok := c.inputQueue.peek()
	if !ok {
		panic("input queue empty after refill")
	}
	input := fuzzInput{
		entry:   entry.(CorpusEntry),
		timeout: workerFuzzDuration,
		warmup:  c.warmupRun(),
	}
	if c.coverageMask != nil {
		input.coverageData = make([]byte, len(c.coverageMask))
		copy(input.coverageData, c.coverageMask)
	}
	if input.warmup {
		// 不会出现模糊，但应计入
		// /-fuzztime设置的限制。
		input.limit = 1
		return input, true
	}

	if c.opts.Limit > 0 {
		input.limit = c.opts.Limit / int64(c.opts.Parallel)
		if c.opts.Limit%int64(c.opts.Parallel) > 0 {
			input.limit++
		}
		remaining := c.opts.Limit - c.count - c.countWaiting
		if input.limit > remaining {
			input.limit = remaining
		}
	}
	return input, true
}

// sentInput在将输入发送到c.inputt后更新内部计数器。
func (c *coordinator) sentInput(input fuzzInput) {
	c.inputQueue.dequeue()
	c.countWaiting += input.limit
}

// refillInputQueue在输入队列变为
// 空后，从语料库中重新填充输入队列。
func (c *coordinator) refillInputQueue() {
	for _, e := range c.corpus.entries {
		c.inputQueue.enqueue(e)
	}
}

// queueForMinimization从结果中创建一个fuzzMinimizeInput，并将其添加到要发送给工作人员的最小化队列中。
func (c *coordinator) queueForMinimization(result fuzzResult, keepCoverage []byte) {
	if result.crasherMsg != "" {
		c.minimizeQueue.clear()
	}

	input := fuzzMinimizeInput{
		entry:        result.entry,
		crasherMsg:   result.crasherMsg,
		keepCoverage: keepCoverage,
	}
	c.minimizeQueue.enqueue(input)
}

// peekMinimizeInput返回下一个输入，该输入应发送给工人进行
// Minimize。
func (c *coordinator) peekMinimizeInput() (fuzzMinimizeInput, bool) {
	if !c.canMinimize() {
		// 对fuzz函数的调用次数已达到最大值。
		// 现在不要发送更多输入。
		return fuzzMinimizeInput{}, false
	}
	v, ok := c.minimizeQueue.peek()
	if !ok {
		return fuzzMinimizeInput{}, false
	}
	input := v.(fuzzMinimizeInput)

	if c.opts.MinimizeTimeout > 0 {
		input.timeout = c.opts.MinimizeTimeout
	}
	if c.opts.MinimizeLimit > 0 {
		input.limit = c.opts.MinimizeLimit
	} else if c.opts.Limit > 0 {
		if input.crasherMsg != "" {
			input.limit = c.opts.Limit
		} else {
			input.limit = c.opts.Limit / int64(c.opts.Parallel)
			if c.opts.Limit%int64(c.opts.Parallel) > 0 {
				input.limit++
			}
		}
	}
	if c.opts.Limit > 0 {
		remaining := c.opts.Limit - c.count - c.countWaiting
		if input.limit > remaining {
			input.limit = remaining
		}
	}
	return input, true
}

// sentMinimizeInput在将输入发送到minimizeC后，从最小化队列中删除该输入。huang jian defg
func (c *coordinator) sentMinimizeInput(input fuzzMinimizeInput) {
	c.minimizeQueue.dequeue()
	c.countWaiting += input.limit
}

// 覆盖率数据，或者在模糊化开始之前测试所有种子语料库
// 是否存在错误。
// 
// 协调器不会将覆盖率数据存储在每个输入
// 的缓存中，因为当测试二进制
// 中的计数器偏移量更改时，该数据将无效。
// 
// 收集覆盖范围时，协调员将每个条目发送给工作人员，以
// 仅收集该条目的覆盖范围，不模糊或最小化。该阶段在所有工作人员完成后结束，协调人有一个组合的
// 覆盖图。
func (c *coordinator) warmupRun() bool {
	return c.warmupInputLeft > 0
}

// updatecorerage在c.coverageMask中设置newCoverage中设置的位。
// updatecorerage返回新设置的位数。格式见
// coverageMask上的注释。
func (c *coordinator) updateCoverage(newCoverage []byte) int {
	if len(newCoverage) != len(c.coverageMask) {
		panic(fmt.Sprintf("number of coverage counters changed at runtime: %d, expected %d", len(newCoverage), len(c.coverageMask)))
	}
	newBitCount := 0
	for i := range newCoverage {
		diff := newCoverage[i] &^ c.coverageMask[i]
		newBitCount += bits.OnesCount8(diff)
		c.coverageMask[i] |= newCoverage[i]
	}
	return newBitCount
}

// 可以最小化返回值，无论协调器是否应该尝试寻找较小的
// 输入，以重现崩溃或新的覆盖范围。
func (c *coordinator) canMinimize() bool {
	return c.minimizationAllowed &&
		(c.opts.Limit == 0 || c.count+c.countWaiting < c.opts.Limit)
}

func (c *coordinator) elapsed() time.Duration {
	return time.Since(c.startTime).Round(1 * time.Second)
}

// readCache从种子值和缓存中的值创建一个组合语料库
// （在GOCACHE/fuzz中）。
// 
// TODO（模糊化）：需要一种机制来删除
// 不再有用的值，例如，因为它们的类型错误。
func (c *coordinator) readCache() error {
	if _, err := c.addCorpusEntries(false, c.opts.Seed...); err != nil {
		return err
	}
	entries, err := ReadCorpus(c.opts.CacheDir, c.opts.Types)
	if err != nil {
		if _, ok := err.(*MalformedCorpusError); !ok {
			// 如果缓存目录中的某些文件格式不正确，并且
			// 未包含在语料库中，则可以，但如果是I/O错误，则会失败。ABCFDG 
			return err
		}
	}
	if _, err := c.addCorpusEntries(false, entries...); err != nil {
		return err
	}
	return nil
}

// 格式错误的CorpUserRor是从
// 文件系统读取语料库时发现的错误。所有错误都存储在错误列表中。测试
// 框架使用它来报告testdata中格式错误的文件。
type MalformedCorpusError struct {
	errs []error
}

func (e *MalformedCorpusError) Error() string {
	var msgs []string
	for _, s := range e.errs {
		msgs = append(msgs, s.Error())
	}
	return strings.Join(msgs, "\n")
}

// ReadCorpus从提供的目录中读取语料库。返回的语料库
// 条目保证与给定类型匹配。任何格式错误的文件都将被保存在格式错误的CorpUserRor中，并与最新的
// 错误一起返回。
func ReadCorpus(dir string, types []reflect.Type) ([]CorpusEntry, error) {
	files, err := ioutil.ReadDir(dir)
	if os.IsNotExist(err) {
		return nil, nil // 没有可阅读的语料库
	} else if err != nil {
		return nil, fmt.Errorf("reading seed corpus from testdata: %v", err)
	}
	var corpus []CorpusEntry
	var errs []error
	for _, file := range files {
		// TODO（jayconrod，katiehockman）：根据文件名确定文件何时是模糊输入
		// 文件。我们应该只读取writeToCorpus创建的文件。
		// 如果我们读取了所有文件，我们将无法通过更改扩展名来更改文件格式。我们也不能添加像
		// README这样的文件。解释目录存在的原因。
		if file.IsDir() {
			continue
		}
		filename := filepath.Join(dir, file.Name())
		data, err := ioutil.ReadFile(filename)
		if err != nil {
			return nil, fmt.Errorf("failed to read corpus file: %v", err)
		}
		var vals []any
		vals, err = readCorpusData(data, types)
		if err != nil {
			errs = append(errs, fmt.Errorf("%q: %v", filename, err))
			continue
		}
		corpus = append(corpus, CorpusEntry{Path: filename, Values: vals})
	}
	if len(errs) > 0 {
		return corpus, &MalformedCorpusError{errs: errs}
	}
	return corpus, nil
}

func readCorpusData(data []byte, types []reflect.Type) ([]any, error) {
	vals, err := unmarshalCorpusFile(data)
	if err != nil {
		return nil, fmt.Errorf("unmarshal: %v", err)
	}
	if err = CheckCorpus(vals, types); err != nil {
		return nil, err
	}
	return vals, nil
}

// CheckCorpus验证VAL中的类型是否与预期的类型
// 已提供。
func CheckCorpus(vals []any, types []reflect.Type) error {
	if len(vals) != len(types) {
		return fmt.Errorf("wrong number of values in corpus entry: %d, want %d", len(vals), len(types))
	}
	valsT := make([]reflect.Type, len(vals))
	for valsI, v := range vals {
		valsT[valsI] = reflect.TypeOf(v)
	}
	for i := range types {
		if valsT[i] != types[i] {
			return fmt.Errorf("mismatched types in corpus entry: %v, want %v", valsT, types)
		}
	}
	return nil
}

// writeToCorpus以原子方式将给定字节写入testdata中的新文件。如果
// 该目录不存在，它将创建一个。如果文件已经存在，
// writeToCorpus将不会重写它。writeToCorpus设置条目。刚写入的新
// 文件的路径，如果失败，则返回错误。
func writeToCorpus(entry *CorpusEntry, dir string) (err error) {
	sum := fmt.Sprintf("%x", sha256.Sum256(entry.Data))
	entry.Path = filepath.Join(dir, sum)
	if err := os.MkdirAll(dir, 0777); err != nil {
		return err
	}
	if err := ioutil.WriteFile(entry.Path, entry.Data, 0666); err != nil {
		os.Remove(entry.Path) // 删除部分写入的文件
		return err
	}
	return nil
}

func testName(path string) string {
	return filepath.Base(path)
}

func zeroValue(t reflect.Type) any {
	for _, v := range zeroVals {
		if reflect.TypeOf(v) == t {
			return v
		}
	}
	panic(fmt.Sprintf("unsupported type: %v", t))
}

var zeroVals []any = []any{
	[]byte(""),
	string(""),
	false,
	byte(0),
	rune(0),
	float32(0),
	float64(0),
	int(0),
	int8(0),
	int16(0),
	int32(0),
	int64(0),
	uint(0),
	uint8(0),
	uint16(0),
	uint32(0),
	uint64(0),
}

var (
	debugInfo     bool
	debugInfoOnce sync.Once
)

func shouldPrintDebugInfo() bool {
	debugInfoOnce.Do(func() {
		debugInfo = godebug.Get("fuzzdebug") == "1"
	})
	return debugInfo
}
